package org.budo.mongo.jdbc.driver.parser.jsqlparser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.budo.jdbc.driver.exception.BudoJdbcDriverNotSupportedException;
import org.budo.jdbc.driver.exception.SubSelectNotSupportedException;
import org.budo.jdbc.driver.parser.AbstractSqlStatementParser;
import org.budo.mongo.jdbc.driver.parser.MongoStatementParser;
import org.budo.mongo.jdbc.driver.statement.MongoStatement;
import org.budo.support.lang.util.MapUtil;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Limit;
import net.sf.jsqlparser.statement.select.OrderByElement;

/**
 * @author lmw
 */
public abstract class AbstractJSqlParserMongoStatementParser extends AbstractSqlStatementParser implements MongoStatementParser {
    protected Object parameterValue(Expression expression) {
        if (null == expression) {
            return null;
        }

        if (expression instanceof StringValue) {
            StringValue stringValue = (StringValue) expression;
            return stringValue.getValue();
        }

        if (expression instanceof LongValue) {
            LongValue longValue = (LongValue) expression;
            return longValue.getValue();
        }

        if (expression instanceof JdbcParameter) {
            JdbcParameter jdbcParameter = (JdbcParameter) expression;
            return MongoStatement.PARAMETER_PREFIX + jdbcParameter.getIndex() + MongoStatement.PARAMETER_SUFFIX;
        }

        throw new BudoJdbcDriverNotSupportedException("#51 expression is " + expression + ", type=" + expression.getClass());
    }

    protected Map<String, Object> orderByDocument(List<OrderByElement> orderByElements) {
        if (null == orderByElements || orderByElements.isEmpty()) {
            return null;
        }

        // 有序的Map
        Map<String, Object> document = new LinkedHashMap<String, Object>();
        for (OrderByElement orderByElement : orderByElements) {
            Map<String, Object> orderBy = this.orderBy_1(orderByElement);
            MapUtil.putAll(document, orderBy);
        }
        return document;
    }

    private Map<String, Object> orderBy_1(OrderByElement orderByElement) {
        int orderValue = orderByElement.isAsc() ? 1 : -1; // 正序倒序

        Expression expression = orderByElement.getExpression();
        if (expression instanceof Column) {
            Column column = (Column) expression;
            String columnName = column.getColumnName();
            return MapUtil.stringObjectMap(columnName, orderValue);
        }

        if (expression instanceof JdbcParameter) {
            JdbcParameter jdbcParameter = (JdbcParameter) expression;
            String columnName = MongoStatement.PARAMETER_PREFIX + jdbcParameter.getIndex() + MongoStatement.PARAMETER_SUFFIX;
            return MapUtil.stringObjectMap(columnName, orderValue);
        }

        throw new BudoJdbcDriverNotSupportedException("#72 orderByElement.expression=" + expression + ", type=" + expression.getClass());
    }

    protected Map<String, Object> limitDocument(Limit limit) {
        if (null == limit) {
            return null;
        }

        Map<String, Object> document = new HashMap<String, Object>();

        Object skip = this.parameterValue(limit.getOffset());
        document.put(MongoStatement.SKIP, skip);

        Object rowCount = this.parameterValue(limit.getRowCount());
        document.put(MongoStatement.LIMIT, rowCount);

        return document;
    }

    protected List<Map<String, Object>> insertDocuments(Insert insert) {
        ItemsList itemsList = insert.getItemsList();
        List<Column> columns = insert.getColumns();

        if (itemsList instanceof ExpressionList) {
            ExpressionList expressionList = (ExpressionList) itemsList;
            List<Expression> expressions = expressionList.getExpressions();

            List<Map<String, Object>> insertDocuments = new ArrayList<Map<String, Object>>();
            Map<String, Object> insertDocument = this.valueDocument(columns, expressions);
            insertDocuments.add(insertDocument);
            return insertDocuments;
        }

        if (itemsList instanceof MultiExpressionList) {
            MultiExpressionList multiExpressionList = (MultiExpressionList) itemsList;
            List<ExpressionList> expressionLists = multiExpressionList.getExprList();

            List<Map<String, Object>> insertDocuments = new ArrayList<Map<String, Object>>();
            for (ExpressionList expressionList : expressionLists) {
                List<Expression> expressions = expressionList.getExpressions();
                Map<String, Object> insertDocument = this.valueDocument(columns, expressions);
                insertDocuments.add(insertDocument);
            }
            return insertDocuments;
        }

        // 异常1
        if (null != insert.getSelect()) {
            throw new SubSelectNotSupportedException("#145 SubSelect not supported, insert=" + insert);
        }

        // 异常2
        throw new BudoJdbcDriverNotSupportedException("#147 itemsList=" + itemsList + ", type=" + (null == itemsList ? "null" : itemsList.getClass()) + ", insert=" + insert);
    }

    protected Map<String, Object> valueDocument(List<Column> columns, List<Expression> expressions) {
        Map<String, Object> document = new HashMap<String, Object>();
        for (int i = 0; i < expressions.size(); i++) {
            Column column = columns.get(i);
            Expression expression = expressions.get(i);

            String key = column.getColumnName();
            Object parameterValue = this.parameterValue(expression);

            document.put(key, parameterValue);
        }
        return document;
    }
}